//==============================================================================
//
//          DFTPClient - the FTP client class in the ofc-library
//
//               Copyright (C) 2006  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-16 12:38:03 $ $Revision: 1.13 $

//==============================================================================

#include "ofc/DFTPClient.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DSocketAddress.h"
#include "ofc/DSocket.h"
#include "ofc/DList.h"
#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"


#define DFPC_PORT       (21)

#define DFPC_PASSIVE    (0)
#define DFPC_ACTIVE     (1)

//
// The DFTPClient class implements a number of methods for building a FTP (File
// Transfer Protocol) client.
//
// @example
// #include <stdio.h>
// #include "ofc/DFTPClient.h"
// 
// int main(int argc, char *argv[])
// {
//   DFTPClient         *ftp   = [DFTPClient new];
//   DInetSocketAddress *addr  = [DInetSocketAddress new];
//   DList              *files = [DList new];
//   DListIterator      *iter  = [DListIterator new];
//   DText              *str;
// 
//   [addr host :"ftp.gnu.org" :DFPC_PORT]; // Set address to the host and default FTP port
// 
//   if ([ftp open :addr])                // Open the ftp client (passive)
//   {
//     if ([ftp waitForGreetings])        // Wait for server greetings
//     {
//       if ([ftp login :NULL :NULL :NULL]) // Login
//       {
//         if (![ftp getWorkingDirectory])
//           printf("Could not do a get working directory:%d\n", [ftp responseCode]);
// 
//         if ([ftp list :NULL :files]) // Ask for a directory listing
//         {
//           printf("Directory with %ld files.\n", [files length]);
// 
//           [iter list :files];
// 
//           str = [iter first];
//           while (str != nil)
//           {
//             printf("File: %s\n", [str cstring]); // Print the files in the listing
// 
//             str = [iter next];
//           }
//         }
//         else
//           printf("Could not do a directory listing:%d\n", [ftp responseCode]);
//       
//       }
//       else
//         printf("Could not login:%d\n", [ftp responseCode]);
//     }
//     else
//       printf("Service not available:%d\n", [ftp responseCode]);
// 
//     [ftp quit];                       // Quit the connection
//   }
//   else
//     printf("Could not connect to \"ftp.gnu.org\".\n");
//   
//   return 0;
// }
// 

@interface DFTPClient : Object
{
@private
  DSocket                *_client;       // the client socket
  DText                  *_request;      // the request for the server
  DText                  *_response;     // the response from the server
  DText                  *_directory;    // the directory info received from the server
  id <DSocketAddressable> _server;       // the address of the server
  id <DSocketAddressable> _address;      // the address received from the server
  int                     _bytes;        // the number of bytes received from the server
  BOOL                    _connected;    // is there a connection to the server ?
  int                     _port;         // the active port (or 0 for passive mode)
  int                     _code;         // the response code
  int                     _bufferSize;   // the buffer size during binary transfer (def. 8192)
}

#endif



@implementation DFTPClient


//// Constructors

//
// Initialise the FTP Client
//
// @return the object
//
- (DFTPClient *) init
{
  [super init];

  _client     = [DSocket new];
  _request    = [DText   new];
  _response   = [DText   new];
  _directory  = [DText   new];
  _server     = nil;
  _address    = nil;
  _bytes      = -1;
  _connected  = NO;
  _port       = 0;
  _code       = -1;
  _bufferSize = 8192;
  
  return self;
}


//// Deconstructor

//
// Free the FTP client object
//
// @return the object
//
- free
{

  [_client    free]; _client   = nil;
  
  [_request   free]; _request  = nil;
  [_response  free]; _response = nil;

  [_directory free]; _directory = nil;

  if (_address != nil)
  {
    [_address free]; _address = nil;
  }
  if (_server != nil)
  {
    [_server free]; _server = nil;
  }
  
  return [super free];
}


//// Copy related methods

//
// Deepen the object (not implemented)
//
//
- deepen
{
  WARNING(DW_METHOD_NOT_IMPL, "deepen");
  
  return self;
}


//// Member methods

//
// Return the local socket
// 
// @return a reference to the socket object
//
- (DSocket *) socket
{
  return _client;
}

//
// Check if there is a connection to the server
// 
// @return is there a connection ?
// 
- (BOOL) isConnected
{
  return _connected;
}

//
// Return the mode of the ftp client
// 
// @return the mode (DFPC_PASSIVE or DFPC_ACTIVE)
// 
- (BOOL) mode
{
  return (_port == 0 ? DFPC_PASSIVE : DFPC_ACTIVE);
}

//
// Set the ftp client in passive mode
// 
// @return the object
// 
- (DFTPClient *) passive
{
  _port = 0;
  
  return self;
}

//
// Set the ftp client in active mode
// 
// @param port     the port to be used during transfer of data with the server
// 
// @return the object
// 
- (DFTPClient *) active :(int) port
{
  _port = port;
  
  return self;
}

//
// Get the last response code
// 
// @return the last response code (1xx-5xx, -1)
// 
- (int) responseCode
{
  return _code;
}

//
// Get the directory name from the last response with a directory name
// 
// @return the directory name (or EOS)
// 
- (const char *) directory
{
  return [_directory cstring];
}


//
// Get the number of bytes received from the server (response 150)
// 
// @return the number of bytes (or -1)
// 
- (int) bytes
{
  return _bytes;
}
  

//
// Set the buffer size during binary transfer
// 
// @param size     the buffer size 
// 
// @return the object
// 
- (DFTPClient *) bufferSize :(int) size
{
  if (size > 0)
  {
    _bufferSize = size;
  }
  else
  {
    WARNING(DW_INVALID_ARG, "size");
  }
  
  return self;
}


//
// Get the buffer size during binary transfer
// 
// @return the size
// 
- (int) bufferSize
{
  return _bufferSize;
}

  
//// Connection methods

//
// Open the FTP client
// 
// @param server        the address of the server
// 
// @return success
// 

- (BOOL) open :(id <DSocketAddressable>) server
{
  BOOL ok = NO;

  if (_connected)
  {
    [self close];
  }
  
  ok  = [_client open :[server family] :DSK_STREAM :[DSocket protocol :"tcp"]];

  if (ok)
  {
    ok = [_client connect :server];
  }
    
  if (ok)
  {
    if (_server != nil)
    {
      [_server free]; _server = nil;
    }
      
    _server = [server copy];
    
    _connected = YES;
  }
  
  return ok;
}

//
// Close the connection to the ftp server
// 
// @return success
// 
- (BOOL) close
{
  BOOL ok = [_client close];
 
  _connected = NO;
  
  return ok;
}


//// Sending and receiving messages

//
// Send a command to the server
// 
// @param command       the command to be sent
// @param parameters    the parameters of the command (or NULL)
// 
// @return success
// 
- (BOOL) sendCommand :(const char *) command :(const char *) parameters
{  
  BOOL ok = NO;
  
  if ((_connected) && (command != NULL) && (*command != EOS))
  {
    [_request set :command];
    
    if (parameters != NULL)
    {
      [_request push   :' '];
      [_request append :parameters];
    }

    // printf("Request:%s\n", [_request cstring]);

    [_request push   :'\r'];
    [_request push   :'\n'];
    
    ok = ([_client send :(void *) [_request cstring] :[_request length] :0] > 0);
  }
  
  return ok;
}

//
// Receive a response from the server
// 
// @return the first digit of the last response code (or -1)
// 
- (int) receiveResponse
{
  BOOL done     = NO;  
  int  contCode = -1;
  
  _code = -1;

  do
  {
    done = YES;
    
    if ([_client recv :_response :16384 :0] > 0)
    {
      while (![_response isEof])
      {
        DText *line = [_response readLine];
        
        if (line != nil)
        {
          _code = [line scanInt :-1];
            
          if (_code == -1)
          {
            if (contCode != -1)
            {
              _code = contCode;
            }
          }
          else
          {
            char ch = [line readChar];
            
            if ((_code == contCode) && (ch != '-'))
            {
              contCode = -1;
            }
            if ((_code != contCode) && (ch == '-'))
            {
              contCode = _code;
            }
          }
          
          if (_code != -1)
          {
            DText *info = [line readText];

            switch (_code)
            {
              case 257: [self processResponse257 :info]; break; // scan for the directory name
              case 227: [self processResponse227 :info]; break; // scan for the passive address
              case 150: [self processResponse150 :info]; break; // scan for number of bytes
              case 229: [self processResponse229 :info]; break; // scan for the passive address IPv6
            }

            [self processResponse :_code :[info cstring]];
            
            [info free];
          }
          
          [line free];
        }
      }
    }
  }
  while (contCode != -1);
    
  return (_code != -1 ? _code / 100 : -1);
}


//// FTP methods

//
// Wait for server greetings
//
// @return success
//
- (BOOL) waitForGreetings
{
  BOOL result;

  do
  {
    result = [self receiveResponse];
  }
  while (result == 1);

  return (result == 2);
}

//
// Do a login on the ftp server
// 
// @param user     the user name (or NULL)
// @param password the password for the user (or NULL)
// @param account  the account of the user (or NULL)
// 
// @return success
// 
- (BOOL) login :(const char *) user :(const char *) password :(const char *) account
{
  BOOL first = -1;
  
  const 
  char anonymous[] = "anonymous";
  const
  char anonpassw[] = "anonymous@";
  
  if ((user == NULL) || (*user == EOS))
  {
    user = anonymous;
  }
  
  if ((strcmp(user, anonymous) == 0) && ((password == NULL) || (*password == EOS)))
  {
    password = anonpassw;
  }
  
  if ([self sendCommand :"USER" :user])
  {
    first = [self receiveResponse];
  }
  else
  {
    first = -1;
  }
  
  if (first == 3)
  {
    if ([self sendCommand :"PASS" :anonpassw])
    {
      first = [self receiveResponse];
    }
    else
    {
      first = -1;
    }
  }
  
  if (first == 3)
  {
    if ([self sendCommand :"ACCT" :account])
    {
      first = [self receiveResponse];
    }
    else
    {
      first = -1;
    }
  }

  return ((first >= 1) && (first <= 3));
}

//
// Use ASCII during data transfer
// 
// @return success
// 
- (BOOL) transferASCII
{
  BOOL ok = NO;
  
  if ([self sendCommand :"TYPE" :"A"])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Use binary during data transfer
// 
// @return success
// 
- (BOOL) transferBinary
{
  BOOL ok = NO;
  
  if ([self sendCommand :"TYPE" :"I"])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the PORT/EPTR commando to the ftp server
// 
// @return a reference to the socket address received from the server (or nil)
// 
- (id <DSocketAddressable>) sendPort
{
  id <DSocketAddressable> server = nil;
  
  if (_port == 0)  // passive mode
  {
    if ([_client family] == DSA_AF_INET)
    {
      if ([self sendCommand :"PASV" :NULL])
      {
        if ([self receiveResponse] == 2)
        {
          server = _address; _address = nil;
        }
      }
    }
    else if ([_client family] == DSA_AF_INET6)
    {
      if ([self sendCommand :"EPSV" :NULL])
      {
        if ([self receiveResponse] == 2)
        {
          server = _address; _address = nil;
        }
      }
    }
  }
  else // ToDo: active port
  {
    if ([_client family] == DSA_AF_INET)
    {
      DText              *text    = [DText new];
      unsigned char       h1,h2,h3,h4;
      int                 port;
      BOOL                ok = NO;
      
      DInetSocketAddress *address = [_server copy];
      
      [address port :_port];
      
      port = [address get :&h1 :&h2 :&h3 :&h4];
      
      [text format :"%u,%u,%u,%u,%u,%u", (unsigned) h1, (unsigned) h2, (unsigned) h3, (unsigned) h4, (unsigned) (port/256), (unsigned) (port%256)];

      if ([self sendCommand :"PORT" :[text cstring]])
      {
        if ([self receiveResponse] == 2)
        {
          server = address;
          
          ok = YES;
        }
      }
      
      if (!ok)
      {
        [address free];
      }
      
      [text free];
    }
    else if ([_client family] == DSA_AF_INET6)
    {
      DText         *text = [DText new];
      unsigned      u16[8];
      unsigned long flowInfo;
      unsigned long scopeId;
      int           port;
      BOOL          ok = NO;
      int           i;
      
      DInet6SocketAddress *address = [_server copy]; 
      
      [address port :_port];
      
      [address get16 :u16 :&port :&flowInfo :&scopeId];
      
      [text format :"|2|%x", u16[0]];
      
      for (i = 1; i < 8; i++)
      {
        [text appendFormat :":%x", u16[i]];
      }
      [text appendFormat :"|%d|", port];
      
      if ([self sendCommand :"EPRT" :[text cstring]])
      {
        if ([self receiveResponse] == 2)
        {
          server = address;
          
          ok = YES;
        }
      }
      
      if (!ok)
      {
        [address free];
      }
      
      [text free];
    }
  }
  
  return server;
}

//
// Change the current directory
// 
// @param directory     the directory
// 
// @return success
// 
- (BOOL) changeCurrentDirectory :(const char *) directory
{
  BOOL ok = NO;
  
  if ((directory == NULL) || (*directory == EOS))
  {
    WARNING(DW_INVALID_ARG, "directory");
  }
  else
  {
    if ([self sendCommand :"CWD" :directory])
    {
      ok = ([self receiveResponse] == 2);
    }
  }
  
  return ok;
}

//
// Change the current directory to the parent directory
// 
// @return success
// 
- (BOOL) changeCurrentToParent
{
  BOOL ok = NO;
  
  if ([self sendCommand :"CDUP" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Mount a different file system
// 
// @param directory     the directory of the file system
// 
// @return success
// 
- (BOOL) mount :(const char *) directory
{
  BOOL ok = NO;
  
  if ([self sendCommand :"SMNT" :directory])
  {
    ok = ([self receiveResponse] == 2);
  }

  return ok;
}

//
// Reinitialize the connection (note: continu with login)
// 
// @return success
// 
- (BOOL) reinitialize
{
  int result = 0;
  
  if ([self sendCommand :"REIN" :NULL])
  {
    do
    {
      result = [self receiveResponse];
    }
    while (result == 1); // Wait for server
  }
  
  return (result == 2);
}

//
// Store ASCII on the ftp server. This method supports two ways of data 
// feeding. If the file parameter is not nil, the ASCII data is read from
// the TextReadable; if it is nil, the data is read by calling the method 
// getASCIIData, which should be overridden.
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (if nil then use getAsciiData)
//
// @return success
// 
- (BOOL) storeASCII :(const char *) command :(const char *) filename :(id <DTextReadable>) file
{
  BOOL ok = NO;
  
  if ([self transferASCII])
  {
    id <DSocketAddressable> server = [self sendPort];
    
    if (server != nil)
    {
      DSocket *conn = [DSocket new];
      
      if ([conn open :[_client family] :[_client type] :[DSocket protocol :"tcp"]])
      {
        if ([conn connect :server])
        {
          _bytes = -1;
          
          if ([self sendCommand :command :filename])
          {
            int resp = [self receiveResponse];
            
            if ((resp == 1) || (resp == 2))
            {
              DText *text = nil;
              BOOL   busy = YES;
              
              while (busy)
              {
                text = nil;
                
                if (file != nil)
                {
                  if (![file isEof])
                  {
                    text = [file readLine];
                  }
                }
                else
                {
                  text = [self getASCIIData];
                }
              
                if (text != nil)
                {
                  [text push :'\r'];
                  [text push :'\n'];
                  
                  busy = ([conn send :[text cstring] :0] > 0);
                  
                  [text free]; text = nil;
                }
                else
                {
                  busy = NO;
                }
              }
            }
            [conn close];
            
            ok = ((resp == 2) || ([self receiveResponse] == 2));
          }
        }
      }
      [conn free];
      [server free];
    }
  }
  
  return ok;
}

//
// Store ASCII on the ftp server, replacing an already existing file
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) overwriteASCII :(const char *) filename :(id <DTextReadable>) file
{
  BOOL ok = NO;
  
  if ((filename == NULL) || (*filename == EOS))
  {
    WARNING(DW_INVALID_ARG, "filename");
  }
  else
  {
    ok = [self storeASCII :"STOR" :filename :file];
  }
  
  return ok;
}

//
// Store ASCII on the ftp server, appending an already existing file
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) appendASCII :(const char *) filename :(id <DTextReadable>) file
{
  BOOL ok = NO;
  
  if ((filename == NULL) || (*filename == EOS))
  {
    WARNING(DW_INVALID_ARG, "filename");
  }
  else
  {
    ok = [self storeASCII :"APPE" :filename :file];
  }
  
  return ok;
}


//
// Store ASCII file with a filename that is unique on the server
// 
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) storeASCIIUnique :(id <DTextReadable>) file
{
  return [self storeASCII :"STOU" :NULL :file];
}


//
// Store Binary data on the ftp server. This method supports two ways of data 
// feeding. If the file parameter is not nil, the binary data is read from
// the DataReadable; if it is nil, the data is read by calling the method 
// getBinaryData, which should be overridden.
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (if nil then use getBinaryData)
//
// @return success
// 
- (BOOL) storeBinary :(const char *) command :(const char *) filename :(id <DDataReadable>) file
{
  BOOL ok = NO;
  
  if ([self transferBinary])
  {
    id <DSocketAddressable> server = [self sendPort];
    
    if (server != nil)
    {
      DSocket *conn = [DSocket new];
      
      if ([conn open :[_client family] :[_client type] :[DSocket protocol :"tcp"]])
      {
        if ([conn connect :server])
        {
          _bytes = -1;
          
          if ([self sendCommand :command :filename])
          {
            int resp = [self receiveResponse];
            
            if ((resp == 1) || (resp == 2))
            {
              DData *data = nil;
              BOOL   busy = YES;
              
              while (busy)
              {
                data = nil;
                
                if (file != nil)
                {
                  if (![file isEof])
                  {
                    data = [file readData :_bufferSize];
                  }
                }
                else
                {
                  data = [self getBinaryData :_bufferSize];
                }
              
                if (data != nil)
                {
                  busy = ([conn send :(void *) [data data] :[data length] :0] > 0);
                  
                  [data free]; data = nil;
                }
                else
                {
                  busy = NO;
                }
              }
            }
            [conn close];
            
            ok = ((resp == 2) || ([self receiveResponse] == 2));
          }
        }
      }
      [conn free];
      [server free];
    }
  }
  
  return ok;
}


//
// Store a binary file on the ftp server, replacing an already existing file
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) overwriteBinary :(const char *) filename :(id <DDataReadable>) file
{
  BOOL ok = NO;
  
  if ((filename == NULL) || (*filename == EOS))
  {
    WARNING(DW_INVALID_ARG, "filename");
  }
  else
  {
    ok = [self storeBinary :"STOR" :filename :file];
  }
  
  return ok;
}

//
// Store a binary file on the ftp server, appending an already existing file
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) appendBinary :(const char *) filename :(id <DDataReadable>) file
{
  BOOL ok = NO;
  
  if ((filename == NULL) || (*filename == EOS))
  {
    WARNING(DW_INVALID_ARG, "filename");
  }
  else
  {
    ok = [self storeBinary :"APPE" :filename :file];
  }
  
  return ok;
}


//
// Store a binary file with a filename that is unique on the server
// 
// @param file          the (opened) file (or nil)
//
// @return success
// 
- (BOOL) storeBinaryUnique :(id <DDataReadable>) file
{
  return [self storeBinary :"STOU" :NULL :file];
}


//
// Retrieve ASCII from the ftp server. This method supports two ways of data 
// receiving. If the file parameter is not nil, the ASCII data is written to
// the TextWritable; if the file parameter is nil, the data is written by 
// calling the method setASCIIData, which should be overridden.
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (if nil use setAsciiData)
//
// @return success
// 
- (BOOL) retrieveASCII :(const char *) filename :(id <DTextWritable>) file
{
  BOOL ok = NO;
  
  if ([self transferASCII])
  {
    id <DSocketAddressable> server = [self sendPort];
    
    if (server != nil)
    {
      DSocket *conn = [DSocket new];
      
      if ([conn open :[_client family] :[_client type] :[DSocket protocol :"tcp"]])
      {
        if ([conn connect :server])
        {
          _bytes = -1;

          if ([self sendCommand :"RETR" :filename])
          {
            int resp = [self receiveResponse];
            
            if ((resp == 1) || (resp == 2))
            {
              DText *text = [DText new];
             
              while ([conn recv :text :"\r\n" :-1 :0] >= 0)
              {
                if (file != nil)
                {
                  [file writeLine :[text cstring]];
                }
                else
                {
                  [self setASCIIData :[text cstring]];
                }
                  
                [text clear];
              }
              [text free];
            }
            [conn close];
            
            ok = ((resp == 2) || ([self receiveResponse] == 2)); // ToDo: bij een error niet doen ??
          }
        }
      }
      [conn free];
      [server free];
    }
  }
  
  return ok;
}


//
// Retrieve binary data from the ftp server. This method supports two ways of 
// data receiving. If the file parameter is not nil, the binary data is written
//  to the DataWritable; if the file parameter is nil, the data is written by 
// calling the method setBinaryData, which should be overridden.
// 
// @param filename      the (path and) name of the file on the server
// @param file          the (opened) file (if nil use setAsciiData)
//
// @return success
// 
- (BOOL) retrieveBinary :(const char *) filename :(id <DDataWritable>) file
{
  BOOL ok = NO;
  
  if ([self transferBinary])
  {
    id <DSocketAddressable> server = [self sendPort];
    
    if (server != nil)
    {
      DSocket *conn = [DSocket new];
      
      if ([conn open :[_client family] :[_client type] :[DSocket protocol :"tcp"]])
      {
        if ([conn connect :server])
        {
          _bytes = -1;
          
          if ([self sendCommand :"RETR" :filename])
          {
            int resp = [self receiveResponse];
            
            if ((resp == 1) || (resp == 2))
            {
              DData *data = nil;
              BOOL   busy = YES;
              
              while (busy)
              {
                data = [conn recv :_bufferSize :0];
                
                if (data != nil)
                {
                  if (file != nil)
                  {
                    [file writeData :[data data] :[data length]];
                  }
                  else
                  {
                    [self setBinaryData :[data data] :[data length]];
                  }
                  
                  [data free];
                }
                else
                {
                  busy = NO;
                }
              }
            }
            [conn close];
            
            ok = ((resp == 2) || ([self receiveResponse] == 2));
          }
        }
      }
      [conn free];
      [server free];
    }
  }
  
  return ok;
}


//
// Send the rename command
// 
// @param oldName       the old filename
// @param newName       the new filename
//
// @return success
// 
- (BOOL) rename :(const char *) oldName :(const char *) newName
{
  BOOL ok = NO;
  
  if ((oldName == NULL) || (*oldName == EOS))
  {
    WARNING(DW_INVALID_ARG, "oldName");
  }
  else if ((newName == NULL) || (*newName == EOS))
  {
    WARNING(DW_INVALID_ARG, "newName");
  }
  else
  {
    if ([self sendCommand :"RNFR" :oldName])
    {
      if ([self receiveResponse] == 3)
      {
        if ([self sendCommand :"RNTO" :newName])
        {
          ok = ([self receiveResponse] == 2);
        }
      }
    }
  }
  
  return ok;
}

//
// Send the delete file to the ftp server
// 
// @param filename      the name of the file
// 
// @return success
// 
- (BOOL) delete :(const char *) filename
{
  BOOL ok = NO;
  
  if ((filename == NULL) || (*filename == EOS))
  {
    WARNING(DW_INVALID_ARG, "filename");
  }
  else
  {
    if ([self sendCommand :"DELE" :filename])
    {
      ok = ([self receiveResponse] == 2);
    }
  }
  
  return ok;
}

//
// Send the remove directory command to the ftp server
// 
// @param directory     the directory name
// 
// @return success
// 
- (BOOL) removeDirectory :(const char *) directory
{
  BOOL ok = NO;
  
  if ((directory == NULL) || (*directory == EOS))
  {
    WARNING(DW_INVALID_ARG, "directory");
  }
  else
  {
    if ([self sendCommand :"RMD" :directory])
    {
      ok = ([self receiveResponse] == 2);
    }
  }
  
  return ok;
}

//
// Send the make directory command to the ftp server.
// Use 'directory' method for the name of the directory in the response.
// 
// @param directory     the directory name
// 
// @return success
// 
- (BOOL) makeDirectory :(const char *) directory
{
  BOOL ok = NO;
  
  if ((directory == NULL) || (*directory == EOS))
  {
    WARNING(DW_INVALID_ARG, "directory");
  }
  else
  {
    if ([self sendCommand :"MKD" :directory])
    {
      ok = ([self receiveResponse] == 2); // ToDo..
    }
  }
  
  return ok;
}

//
// Send the request for the current working directory to the ftp server.
// Use 'directory' method for the directory name in the response.
// 
// @return success
// 
- (BOOL) getWorkingDirectory
{
  BOOL ok = NO;
    
  if ([self sendCommand :"PWD" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}


//
// Send a list commando (LIST or NLST) (private)
// 
// @param command       the command to be sent
// @param parameter     the parameter (or NULL)
// @param list          the list to store the information (or nil)
// 
// @return success
// 
- (BOOL) sendListCommand :(const char *) command :(const char *) parameter :(DList *) list
{
  BOOL ok = NO;
  
  if ([self transferASCII])
  {
    id <DSocketAddressable> server = [self sendPort];
    
    if (server != nil)
    {
      DSocket *conn = [DSocket new];
      
      if ([conn open :[_client family] :[_client type] :[DSocket protocol :"tcp"]])
      {
        if ([conn connect :server])
        {
          _bytes = -1;
          
          if ([self sendCommand :command :parameter])
          {
            int resp = [self receiveResponse];
            
            if ((resp == 1) || (resp == 2))
            {
              DText *data = [DText new];
              
              while ([conn recv :data :16384 :0] > 0)
              {
                while (![data isEof])
                {
                  DText *line = [data readLine];
        
                  if (line != nil)
                  {
                    if (list != nil)
                    {
                      [list append :line];
                    }
                    else
                    {
                      [self processListLine :[line cstring]];
            
                      [line free];
                    }
                  }
                }
              }
              
              [data free];
            
              ok = ((resp == 2) || ([self receiveResponse] == 2));
            }
          }
        }
        
        [conn close];
      }
      [conn free];
      [server free];
    }
  }
  
  return ok;
}

//
// Send the request for a list to the ftp server. If the list is not
// nil, the received names will be stored in the list. If it is nil, the
// method processListLine will be called for every received list line.
// 
// @param name          the directory or filename (or NULL)
// @param list          the list to store the received info (or nil)
// 
// @return success
// 
- (BOOL) list :(const char *) name :(DList *) list
{
  return [self sendListCommand :"LIST" :name :list];
}

//
// Send the request for site parameters to the ftp server
// 
// @param parameter     the parameter
// 
// @return success
// 
- (BOOL) siteParameter :(const char *) parameter
{
  BOOL ok = NO;
  
  if ((parameter == NULL) || (*parameter == EOS))
  {
    WARNING(DW_INVALID_ARG, "parameter");
  }
  else if ([self sendCommand :"SITE" :parameter])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the request for system information to the ftp server
// 
// @return success
// 
- (BOOL) systemInfo
{
  BOOL ok = NO;
  
  if ([self sendCommand :"SYST" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the request for status to the ftp server
// 
// @return success
// 
- (BOOL) state
{
  BOOL ok = NO;
  
  if ([self sendCommand :"STAT" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the request for help to the ftp server
// 
// @param key           the key for the help (or NULL)
// 
// @return success
// 
- (BOOL) help :(const char *) key
{
  BOOL ok = NO;
  
  if ([self sendCommand :"HELP" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the request for noop to the ftp server
// 
// @return success
// 
- (BOOL) noop
{
  BOOL ok = NO;
  
  if ([self sendCommand :"NOOP" :NULL])
  {
    ok = ([self receiveResponse] == 2);
  }
  
  return ok;
}

//
// Send the request for a name list to the ftp server. If the list is not
// nil, the received names will be stored in the list. If it is nil, the
// method processListLine will be called for every received list line.
// 
// @param directory     the directory name (or NULL)
// @param list          the list to store the received names (or nil)
// 
// @return success
// 
- (BOOL) nameList :(const char *) directory :(DList *) list
{
  return [self sendListCommand :"NLST" :directory :list];
}

//
// Send the QUIT command to the ftp server (and close the connnection)
// 
// @return success
// 
- (BOOL) quit
{
  BOOL ok = NO;
  
  if ([self sendCommand :"QUIT" :NULL])
  {
    if ([self receiveResponse] == 2)
    {
      ok = YES;
      
      [self close];
    }
  }
  
  return ok;
}


//// Data feeders

//
// Get ASCII data during an ASCII transfer (to be overridden)
// 
// @return a (new) text string with ASCII data to be stored (without CR and/or LF) (or nil for done)
// 
- (DText *) getASCIIData
{
  return nil;
}


//
// Get binary data during an binary transfer (to be overridden)
// 
// @param bufferSize     the size of the buffer
// 
// @return a (new) data string with data to be stored (or nil for done)
// 
- (DData *) getBinaryData :(int) bufferSize
{
  return nil;
}


//// Data savers

//
// Set ASCII data during an ASCII retrieve (to be overridden)
// 
// @param cstring        the string with the ASCII data (no end of line)
// 
- (void) setASCIIData :(const char *) cstring
{
  // nothing
}


//
// Set binary data during an binary retrieve (to be overridden)
// 
// @param data           the data
// @param length         the length of the data
// 
- (void) setBinaryData :(const unsigned char *) data :(unsigned long) length
{
  // nothing
}


//// Process responses

//
// Process the receiving of a response (to be overridden)
// 
// @param code          the response code 1xx - 5xx
// @param response      the response text
// 
// @return none
// 
- (void) processResponse :(int) code :(const char *) response
{
  printf("%d %s\n", code, response);
}

//
// Process the receiving of list information (to be overridden)
// 
// @param line          one line of the list information
// 
// @return none
// 
- (void) processListLine :(const char *) line
{
  printf("List: %s\n", line);
}

//
// Process the 257 response (the directory name)
// 
// @param response      the response text
// 
// @return none
// 
- (void) processResponse257 :(DText *) response
{
  [_directory clear];
  
  [response skipWhiteSpace];
  
  if ([response imatch :"\""])
  {
    DText *scanned = [response scanText :'"'];
    
    while (scanned != nil)
    {
      [_directory append :[scanned cstring]];
     
      [scanned free]; scanned = nil;
      
      if ([response imatch :"\""])
      {
        scanned = [response scanText :'"'];
      }
    }
  }
}

//
// Process the 227 response (the inet address)
// 
// @param response      the response text
// 
// @return none
// 
- (void) processResponse227 :(DText *) response
{
  DText *txt = [response scanText :'('];
  
  if (_address != nil)
  {
    [_address free]; _address = nil;
  }
  
  if (txt != nil)
  {
    int nr[6];     // 4 address bytes and 2 port bytes
    int i   = 0;
    
    BOOL ok = YES;
    
    while ((i < sizeof(nr) / sizeof(nr[0])) && (ok)) 
    {
      nr[i] = [response scanInt :-1];
      
      ok = ((nr[i] >= 0) && (nr[i] < 256));

      if (ok)
      {
        [response skipChar :','];
      }
      
      i++;
    }
    
    if (ok)
    {
      DInetSocketAddress *address = [DInetSocketAddress alloc];

      [address init :(unsigned char) nr[0] :(unsigned char) nr[1] :(unsigned char) nr[2] :(unsigned char) nr[3] :((nr[4] << 8) + nr[5])];

      _address = address;
    }
    
    [txt free];
  }
}

//
// Process the 150 response (the byte size)
// 
// @param response      the response text
// 
// @return none
// 
- (void) processResponse150 :(DText *) response
{
  DText *txt = [response scanText :'('];
  
  if (txt != nil)
  {
    long bytes;
    
    bytes = [response scanInt :-1];
    
    if (bytes >= 0)
    {
      [response skipWhiteSpace];
      
      if ([response imatch :"bytes)"])
      {
        _bytes = bytes;
      }
    }
    
    [txt free];
  }
}

//
// Process the 229 response (the inet address for IPv6)
// 
// @param response      the response text
// 
// @return none
// 
- (void) processResponse229 :(DText *) response
{
  DText *txt = [response scanText :'('];
  
  if (txt != nil)
  {
    char sep;
    int  port;
    
    sep = [response readChar];
    
    [response skipChar :sep];
    
    port = [response scanInt :-1];
      
    if ((port > 0) && ([response readChar] == sep))
    {
      _address = _server;
      
      [(DInet6SocketAddress *)_address port :port];
    }
    
    [txt free];
  }
}

@end

/*===========================================================================*/
